<?php

/**
 * ${NAMESPACE}\Later
 *
 * @author Robin Fai <robinfai9@gmail.com>
 * @link http://widget.robinfai.com
 * @license http://www.yiiframework.com/license/
 * @version $Id: $ 14-8-31 7:30 robin.fai $
 * @property array $stack
 */
class Profiler extends CComponent {
    public $whiteList = array();
    public $logTable = 'profiling_log';

    public $minProfilingTime = 0;

    private $controllerID;
    private $actionID;

    const END_TIME_KEY = 'end_time';

    /**
     * 初始化
     */
    public function init() {
        $db = Yii::app()->db;
        if ($db->schema->getTable($this->logTable) === null) {
            $this->createLogTable();
        }
        $this->setControllerAction();


        $profiler                  = $this;
        Yii::app()->onBeginRequest = function () use ($profiler) {
            $profiler->begin('request');
        };
        Yii::app()->onEndRequest   = function () use ($profiler) {
            $profiler->end('request');
            $profilingTime = ($profiler->stack[0][self::END_TIME_KEY] - $profiler->stack[0]['begin_time']) * 1000;
            try {
                if ($profilingTime > $this->minProfilingTime) {
                    Yii::app()->db->createCommand()->insert($this->logTable, array(
                        'uri'           => Yii::app()->request->url,
                        'controller'    => $profiler->controllerID,
                        'action'        => $profiler->actionID,
                        'logTime'       => date('Y-m-d H:i:s'),
                        'getParams'     => json_encode($_GET),
                        'postParams'    => json_encode($_POST),
                        'profilingLog'  => json_encode($profiler->stack),
                        'profilingTime' => $profilingTime,
                        //记录的单位为ms
                    ));
                }
            } catch (Exception $e) {
                Yii::log('profiler日志记录失败');
            }
        };
    }

    /**
     * 重新获取Controller、action
     */
    private function setControllerAction() {
        if($this->controllerID && $this->actionID){
            return $this;
        }
        if (is_array(Yii::app()->catchAllRequest) && isset(Yii::app()->catchAllRequest[0])) {
            $route = Yii::app()->catchAllRequest[0];
            foreach (array_splice(Yii::app()->catchAllRequest, 1) as $name => $value) $_GET[$name] = $value;
        } else
            $route = Yii::app()->getUrlManager()->parseUrl(Yii::app()->getRequest());
        if (($ca = Yii::app()->createController($route)) !== null) {
            list($controller, $actionID) = $ca;
            /* @var $controller CController */
            $this->controllerID = $controller->uniqueId;
            $this->actionID     = $actionID ? : $controller->defaultAction;
        }
    }

    public $_stack = array();
    public $position = 1;

    /**
     * 获取只读的日志堆栈
     * @return array
     */
    public function getStack() {
        return $this->_stack;
    }

    /**
     * 开始性能日志记录
     * @param $token
     */
    public function begin($token) {
        $trace          = debug_backtrace();
        $caller         = $trace[0];
        $file           = $caller['file'];
        $line           = $caller['line'];
        $file           = str_replace(Yii::app()->basePath, '', $file);
        $this->position++;
        $this->_stack[] = array(
            'token'            => $token,
            'begin_file'       => $file,
            'begin_line'       => $line,
            'begin_time'       => microtime(true),
            'end_file'         => null,
            'end_line'         => null,
            self::END_TIME_KEY => null,
            'left'             => $this->position,
            'right'            => null,
        );
    }

    /**
     * 结束性能日志记录，通过左右值方式循环查找对应的节点。
     * @param $token
     * @throws Exception
     */
    public function end($token) {
        $trace  = debug_backtrace();
        $caller = $trace[0];
        $file   = $caller['file'];
        $line   = $caller['line'];
        $file   = str_replace(Yii::app()->basePath, '', $file);

        $stack = end($this->_stack);
        do {
            if (!$stack['right']) {
                if ($token != $stack['token']) {
                    throw new Exception('profiler stack error');
                }
                $stack['end_file']                = $file;
                $stack['end_line']                = $line;
                $stack[self::END_TIME_KEY]        = microtime(true);
                $stack['right']                   = $this->position++;
                $this->_stack[key($this->_stack)] = $stack;
                break;
            }
        } while ($stack = prev($this->_stack));
    }


    /**
     * 创建性能日志数据表
     */
    public function createLogTable() {
        $db = Yii::app()->db;

        $db->createCommand()->createTable($this->logTable, array(
            'id'            => 'pk',
            'uri'           => 'string',
            'controller'    => 'string',
            'action'        => 'string',
            'logTime'       => 'datetime',
            'getParams'     => 'text',
            'postParams'    => 'text',
            'profilingLog'  => 'text',
            'profilingTime' => 'float',
        ));
    }

}